/*
 * Copyright 2010, Alfresco Software, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package controllers;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import opencmis.CMIS;

import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.QueryResult;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;
import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage;

import play.mvc.Controller;

public class Page extends Controller {

	public static final String HOMEPAGE = "home";

	/**
	 * Index controller.
	 */
	public static void index() {
		redirect("Page.show", HOMEPAGE);
	}

	/**
	 * Show controller.
	 */
	public static void show(String page) {
		String pageTitle = page;
		String pageContent = "";

		if (page == null) {
			redirect("Page.show", HOMEPAGE);
		}

		Document wikiPage = getWikiPage(page);

		if (wikiPage == null) {
			redirect("Page.edit", page);
		} else {
			String rawContent = getDocumentContent(wikiPage);
			pageContent = getHtmlFromMarkup(rawContent);
		}

		render(pageTitle, pageContent);
	}

	/**
	 * Edit controller.
	 */
	public static void edit(String page) {
		String pageTitle = page;
		String pageContent = "";

		Document wikiPage = getWikiPage(page);

		if (wikiPage == null) {
			pageContent = "New content goes here...";
		} else {
			pageContent = getDocumentContent(wikiPage);
		}

		render(pageTitle, pageContent);
	}

	/**
	 * Save action.
	 */
	public static void save(String title, String orgTitle, String content) {

		Document wikiPage = getWikiPage(orgTitle);

		if (wikiPage == null) {
			createPage(title, content);
		} else {
			updatePage(wikiPage, title, content, !title.equals(orgTitle));
		}

		show(title);
	}

	/**
	 * Deleye action.
	 */
	public static void delete(String page) {

		if (page == null) {
			redirect("Page.show", HOMEPAGE);
		}

		Document wikiPage = getWikiPage(page);

		if (wikiPage != null) {
			wikiPage.delete(true);
		}

		redirect("Page.show", HOMEPAGE);
	}

	/**
	 * History controller.
	 */
	public static void history(String page) {
		String pageTitle = page;

		if (page == null) {
			redirect("Page.show", HOMEPAGE);
		}

		Document wikiPage = getWikiPage(page);
		List<Document> versions = wikiPage.getAllVersions();

		render(pageTitle, versions);
	}

	/**
	 * Version controller.
	 */
	public static void version(String page, String version) {
		String pageTitle = page;
		String pageVersion = version;
		String pageContent = "";

		if (page == null) {
			redirect("Page.show", HOMEPAGE);
		}

		if (version == null) {
			redirect("Page.show", page);
		}

		Document wikiPage = getWikiPage(page);
		List<Document> versions = wikiPage.getAllVersions();

		boolean found = false;
		for (Document docVersion : versions) {
			if (version.equals(docVersion.getVersionLabel())) {
				String rawContent = getDocumentContent(docVersion);
				pageContent = getHtmlFromMarkup(rawContent);

				found = true;
				break;
			}
		}

		if (!found) {
			error("Version not found!");
			return;
		}

		render(pageTitle, pageVersion, pageContent);
	}

	/**
	 * Search controller.
	 */
	public static void search(String q) {
		List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();

		if ((q != null) && (q.length() > 0)) {
			// get type definition
			String wikiType = CMIS.getWikiDocumentType();
			TypeDefinition type = CMIS.getSession().getTypeDefinition(wikiType);

			String typeQueryName = type.getQueryName();

			Map<String, PropertyDefinition<?>> propertyDefinitions = type.getPropertyDefinitions();

			String nameQueryName = propertyDefinitions.get("cmis:name").getQueryName();
			String modDateQueryName = propertyDefinitions.get("cmis:lastModificationDate").getQueryName();

			// query
			String cql = "SELECT " + nameQueryName + " as name, " + modDateQueryName + " as date, SCORE() as sc " + //
					"FROM " + typeQueryName + " WHERE CONTAINS('" + q + "')";

			ItemIterable<QueryResult> queryResult = CMIS.getSession().query(cql, false);

			for (QueryResult qr : queryResult) {
				Map<String, Object> result = new HashMap<String, Object>();

				result.put("name", qr.getPropertyValueByQueryName("name"));
				result.put("date", qr.getPropertyValueByQueryName("date"));
				result.put("score", qr.getPropertyValueByQueryName("sc"));

				results.add(result);
			}
		}

		render(q, results);
	}

	/**
	 * Fetches a Wiki page or returns <code>null</code> if the page does not
	 * exist.
	 */
	private static Document getWikiPage(String page) {
		String path = CMIS.getWikiRootFolder() + "/" + page;

		CmisObject wikiPage = null;
		try {
			wikiPage = CMIS.getSession().getObjectByPath(path);
			wikiPage.refreshIfOld(2 * 1000);

			if (!(wikiPage instanceof Document)) {
				error("Wiki page is not a document!");
				return null;
			}

			return (Document) wikiPage;
		} catch (CmisObjectNotFoundException onf) {
			return null;
		}
	}

	/**
	 * Creates a new page.
	 */
	private static void createPage(String title, String content) {
		String wikiFolderRootPath = CMIS.getWikiRootFolder();

		CmisObject wikiRootObject = CMIS.getSession().getObjectByPath(wikiFolderRootPath);
		if (!(wikiRootObject instanceof Folder)) {
			error("Root folder is not a folder!");
			return;
		}

		Folder wikiRootFolder = (Folder) wikiRootObject;

		Map<String, Object> properties = new java.util.HashMap<String, Object>();
		properties.put(PropertyIds.NAME, title);
		properties.put(PropertyIds.OBJECT_TYPE_ID, CMIS.getWikiDocumentType());

		ContentStream contentStream = getContentStream(title, content);

		wikiRootFolder.createDocument(properties, contentStream, VersioningState.MAJOR);
	}

	/**
	 * Updates an existing page.
	 */
	private static void updatePage(Document doc, String title, String content, boolean rename) {

		ObjectId pwcId = doc.checkOut();
		Document pwc = (Document) CMIS.getSession().getObject(pwcId);

		Map<String, Object> properties = new java.util.HashMap<String, Object>();

		if (rename) {
			properties.put(PropertyIds.NAME, title);
		}

		ContentStream contentStream = getContentStream(title, content);

		pwc.checkIn(true, properties, contentStream, null);
	}

	/**
	 * Gets the content of a document and returns it as a string.
	 */
	private static String getDocumentContent(Document doc) {
		ContentStream contentStream = doc.getContentStream();

		if (contentStream == null) {
			return "";
		}

		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(contentStream.getStream(), "UTF-8"));

			StringBuilder sb = new StringBuilder();
			String s;
			while ((s = reader.readLine()) != null) {
				sb.append(s);
				sb.append("\n");
			}

			reader.close();

			return sb.toString();
		} catch (Exception e) {
			error("Unable to read content!");
			return "";
		}
	}

	/**
	 * Creates a ContentStream object from a page title and page content.
	 */
	private static ContentStream getContentStream(String title, String content) {
		byte[] bytes = null;
		try {
			bytes = content.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
		}

		InputStream stream = new ByteArrayInputStream(bytes);

		return new ContentStreamImpl(title, BigInteger.valueOf(bytes.length), "text/plain", stream);
	}

	/**
	 * Converts MediaWiki markup into HTML.
	 */
	private static String getHtmlFromMarkup(String markup) {
		StringWriter writer = new StringWriter();
		HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer);
		builder.setEmitAsDocument(false);
		try {
			builder.setBase(new URI(request.getBase() + "/page/show"));
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}

		MediaWikiLanguage language = new MediaWikiLanguage();
		language.setInternalLinkPattern("/{0}");

		MarkupParser markupParser = new MarkupParser();
		markupParser.setMarkupLanguage(language);
		markupParser.setBuilder(builder);
		markupParser.parse(markup);

		return writer.toString();
	}
}
